﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using MinecraftEnchantCalculator.Abstractions;
using MinecraftEnchantCalculator.Data;
using MinecraftEnchantCalculator.Data.Entity;
using MinecraftEnchantCalculator.Enums;
using MinecraftEnchantCalculator.Models;
using MinecraftEnchantCalculator.ViewModels.Windows.MainWindowComponents;
using EnchantViewModel = MinecraftEnchantCalculator.ViewModels.Windows.MainWindowComponents.EnchantViewModel;
using ItemViewModel = MinecraftEnchantCalculator.ViewModels.Windows.MainWindowComponents.ItemViewModel;

namespace MinecraftEnchantCalculator.ViewModels.Windows;

public partial class MainWindowViewModel : ViewModelBase
{
  private static readonly IAnvil _Anvil = Ioc.Default.GetRequiredService<IAnvil>();
  [ObservableProperty] private int _expCost;
  [ObservableProperty] private int _expMax;

  [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(CalculateCommand))] private ItemViewModel? _selectedItem;
  [ObservableProperty] private int _step;

  public MainWindowViewModel()
  {
    Items = new ObservableCollection<ItemViewModel>();
    foreach (var item in Db.Items.Rows) {
      var itemWrapper = new ItemViewModel(item);
      itemWrapper.PropertyChanged += ItemWrapperOnPropertyChanged;
      Items.Add(itemWrapper);
    }
  }

  public ObservableCollection<ItemViewModel> Items { get; }
  public ObservableCollection<EnchantViewModel> InitEncs { get; } = new();
  public ObservableCollection<EnchantViewModel> EnchantBooks { get; } = new();
  public ObservableCollection<ResultView> Results { get; } = new();

  private void ItemWrapperOnPropertyChanged(
    object? sender, PropertyChangedEventArgs e
  )
  {
    if (e.PropertyName != nameof(ItemViewModel.IsSelected))
      return;

    var itemWrapper = (sender as ItemViewModel)!;
    if (!itemWrapper.IsSelected)
      return;

    InitEncs.Clear();
    EnchantBooks.Clear();
    var (_, applyEncCodes) = Db.Applicables.Rows.First(applicable => applicable.ItemCode == itemWrapper.Item.Code);
    foreach (var enchantment in Db.Enchantments.Rows.Where(enc => applyEncCodes.Contains(enc.Code))) {
      InitEncs.Add(NewEncWrapper(enchantment, SpellType.INIT));
      EnchantBooks.Add(NewEncWrapper(enchantment, SpellType.CANDIDATE));
    }
  }

  private EnchantViewModel NewEncWrapper(
    Enchantment enchantment, SpellType spellType
  )
  {
    var encWrapper = new EnchantViewModel(enchantment, spellType);
    encWrapper.PropertyChanged += EncWrapperOnPropertyChanged;
    return encWrapper;
  }

  private void EncWrapperOnPropertyChanged(
    object? sender, PropertyChangedEventArgs e
  )
  {
    if (e.PropertyName != nameof(EnchantViewModel.IsSelected))
      return;

    var encWrapper = (sender as EnchantViewModel)!;
    var other = encWrapper.Spell switch {
      SpellType.INIT => EnchantBooks.First(x => x.Enchantment.Code == encWrapper.Enchantment.Code),
      SpellType.CANDIDATE => InitEncs.First(x => x.Enchantment.Code == encWrapper.Enchantment.Code),
      _ => throw new ArgumentOutOfRangeException(nameof(encWrapper.Spell))
    };
    
    var cft = Db.Conflicts.Rows.FirstOrDefault(x => x.EnchantmentCode == encWrapper.Enchantment.Code);
    if (cft is not null)
      foreach (var cftCode in cft.ConflictCodes) {
        foreach (var enchantBook in EnchantBooks)
          if (enchantBook.Enchantment.Code == cftCode)
            enchantBook.IsEnabled = !encWrapper.IsSelected && !other.IsSelected;

        foreach (var initEnc in InitEncs)
          if (initEnc.Enchantment.Code == cftCode)
            initEnc.IsEnabled = !encWrapper.IsSelected && !other.IsSelected;
      }

    if (encWrapper.Spell == SpellType.CANDIDATE)
      CalculateCommand.NotifyCanExecuteChanged();
  }

  [RelayCommand(CanExecute = nameof(CanCalculate))] private void Calculate()
  {
    Results.Clear();

    var enchantables = new List<IEnchantable>();
    var targetItem = new EnchantItem { ItemEntity = SelectedItem!.Item };
    targetItem.Enchantments.AddRange(InitEncs.Where(e => e.IsSelected).Select(x => x.Enchantment));
    enchantables.Add(targetItem);

    enchantables.AddRange(EnchantBooks.Where(e => e.IsSelected).Select(x => {
      var book = new EnchantBook();
      book.Enchantments.Add(x.Enchantment);
      return book;
    }));

    var step = 0;
    var expCost = 0;
    var expMax = 0;
    _Anvil.Enchant(enchantables, (left, right, result) => {
      Results.Add(new ResultView {
        Left = left, Right = right, Result = result
      });

      ++step;
      expCost += result.Cost;
      expMax = Math.Max(expMax, result.Cost);
    });

    Step = step;
    ExpCost = expCost;
    ExpMax = expMax;
  }

  private bool CanCalculate()
  {
    return SelectedItem != null && EnchantBooks.Any(x => x.IsSelected);
  }
}